package com.lj.common.utils.qywx;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.lj.common.exception.CommonException;
import com.lj.common.utils.FrequencyLimiter;
import com.lj.common.utils.qywx.builder.*;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/**
 * @author luojing
 * @since 2025/3/21 18:11
 * 企业微信机器人
 * 文档: <a href="https://developer.work.weixin.qq.com/document/path/91770">企业微信群机器人配置说明</a>
 */
@Slf4j
public class QywxQunBot {

    private static final String webhookUrl = "https://qyapi.weixin.qq.com/cgi-bin/webhook/";
    /**
     * 企业微信机器人 的webhookKey
     */
    private final String webhookKey;

    /**
     * 企业微信群机器人要求 每个机器人发送的消息不能超过20条/分钟。
     * 为什么这里限制的是19而不是20，因为防止在时间窗口间隔的时候触发限制
     * 如果超过了这个限制，企业微信会报：api freq out of limit
     */
    private final FrequencyLimiter limiter = new FrequencyLimiter(19, 1, TimeUnit.MINUTES);

    public QywxQunBot(String webhookKey) {
        this.webhookKey = webhookKey;
    }

    /**
     * 异步发送模板卡片
     */
    public void asyncSendTemplateCard(TemplateCardBuilder templateCardBuilder) {
        this.asyncSend(MsgType.TemplateCard, templateCardBuilder.build());
    }

    /**
     * 发送模板卡片
     */
    public void sendTemplateCard(TemplateCardBuilder templateCardBuilder) {
        this.send(MsgType.TemplateCard, templateCardBuilder.build());
    }

    /**
     * 异步发送语音
     */
    public void asyncSendVoice(FileVoiceBuilder fileVoiceBuilder) {
        this.asyncSend(MsgType.Voice, fileVoiceBuilder.build());
    }

    /**
     * 发送语音
     */
    public void sendVoice(FileVoiceBuilder fileVoiceBuilder) {
        this.send(MsgType.Voice, fileVoiceBuilder.build());
    }

    /**
     * 异步发送文件
     */
    public void asyncSendFile(FileVoiceBuilder fileVoiceBuilder) {
        this.asyncSend(MsgType.File, fileVoiceBuilder.build());
    }

    /**
     * 发送文件
     */
    public void sendFile(FileVoiceBuilder fileVoiceBuilder) {
        this.send(MsgType.File, fileVoiceBuilder.build());
    }

    /**
     * 异步发送图文
     */
    public void asyncSendNews(NewsBuilder newsBuilder) {
        this.asyncSend(MsgType.News, newsBuilder.build());
    }

    /**
     * 发送图文
     */
    public void sendNews(NewsBuilder newsBuilder) {
        this.send(MsgType.News, newsBuilder.build());
    }

    /**
     * 异步发送图片
     */
    public void asyncSendImage(ImageBuilder imageBuilder) {
        this.asyncSend(MsgType.Image, imageBuilder.build());
    }

    /**
     * 发送图片
     */
    public void sendImage(ImageBuilder imageBuilder) {
        this.send(MsgType.Image, imageBuilder.build());
    }

    /**
     * 异步发送markdown
     */
    public void asyncSendMarkdown(MarkdownBuilder markdownBuilder) {
        this.asyncSend(MsgType.Markdown, markdownBuilder.build());
    }

    /**
     * 发送markdown
     */
    public void sendMarkdown(MarkdownBuilder markdownBuilder) {
        this.send(MsgType.Markdown, markdownBuilder.build());
    }

    /**
     * 异步发送文本
     */
    public void asyncSendText(TextBuilder textBuilder) {
        this.asyncSend(MsgType.Text, textBuilder.build());
    }

    /**
     * 发送文本
     */
    public void sendText(TextBuilder textBuilder) {
        this.send(MsgType.Text, textBuilder.build());
    }

    /**
     * 异步发送消息
     *
     * @param msgType 消息类型
     * @param params  消息参数
     */
    public void asyncSend(MsgType msgType, Map<String, Object> params) {
        ThreadUtil.execute(() -> this.send(msgType, params));
    }

    /**
     * 发送消息
     *
     * @param msgType 消息类型
     * @param params  消息参数
     */
    public void send(MsgType msgType, Map<String, Object> params) {
        HttpRequest request = HttpRequest.post(webhookUrl + "send?key=" + webhookKey)
                .body(JSONUtil.toJsonStr(MapUtil.<String, Object>builder()
                        .put("msgtype", msgType.getValue())
                        .put(msgType.getValue(), params)
                        .build()));
        execute(request, msgType);
    }

    /**
     * 异步上传文件
     *
     * @param msgType   文件类型，分别有语音(voice)和普通文件(file)
     * @param mediaFile 上传的文件
     * @return media_id
     */
    public Future<String> asyncUploadMedia(MsgType msgType, File mediaFile) {
        return ThreadUtil.execAsync(() -> this.uploadMedia(msgType, mediaFile));
    }

    /**
     * 上传文件到企业微信
     * 素材上传得到media_id，该media_id仅三天内有效
     * media_id只能是对应上传文件的机器人可以使用
     *
     * @param msgType   文件类型，分别有语音(voice)和普通文件(file)
     * @param mediaFile 上传的文件
     * @return media_id
     */
    public String uploadMedia(MsgType msgType, File mediaFile) {
        if (!(MsgType.File.equals(msgType) || MsgType.Voice.equals(msgType))) {
            throw new CommonException("只支持文件与语音的上传");
        }
        HttpRequest request = HttpRequest.post(webhookUrl + "upload_media?key=" + webhookKey + "&type=" + msgType.getValue())
                .form(MapUtil.<String, Object>builder()
                        .put("media", mediaFile)
                        .build());
        return execute(request, msgType).getStr("media_id");
    }

    /**
     * 具体执行，向企业微信发送请求，返回json
     *
     * @param request 请求体
     * @param msgType 消息类型
     * @return 企业微信响应的json
     */
    private JSONObject execute(HttpRequest request, MsgType msgType) {
        // 防止达到限制时日志刷屏
        boolean flagLog = true;
        while (limiter.check()) {
            // 达到频率限制就等待下个时间窗口请求
            if (flagLog) {
                log.warn("企业微信机器人请求达到限制, 等待下一个时间窗口...");
                flagLog = false;
            }
            ThreadUtil.sleep(1000);
        }
        try (HttpResponse execute = request.execute()) {
            if (execute.getStatus() != 200) {
                throw new CommonException("网络异常! 无法请求到企业微信!");
            }
            // 正常情况: {"errcode":0,"errmsg":"ok"}
            String body = execute.body();
            JSONObject jsonObject = JSONUtil.parseObj(body);
            if (jsonObject.getInt("errcode") == null || jsonObject.getInt("errcode") > 0) {
                log.error("发送 [{}] 失败: {}", msgType.getDesc(), jsonObject.getStr("errmsg"));
                throw new CommonException("发送 [" + msgType.getDesc() + "] 失败: " + jsonObject.getStr("errmsg"));
            }
            return jsonObject;
        }
    }
}
